import { computed, type Ref } from 'vue'
import type { ChatSendingData } from './type'
import type { MessageInfo } from 'ant-design-x-vue'
import type { BubbleDataType } from 'node_modules/ant-design-x-vue/typings/bubble/interface'
import { useXAgent, useXChat } from 'ant-design-x-vue'
import { t } from '@/i18n'
import xModelService from '@/services/model'
import xChatService from './index'
import { renderChatBubbles } from './hooks/x-bubbles'

/**
 * 聊天代理类（ant-design-x-vue）
 *
 * @description 处理聊天会话实时交互数据
 */
class xChatAgent {
  // 静态属性存储唯一实例
  private static instance: xChatAgent

  // 当前请求的聊天数据
  private static chatSendingData: ChatSendingData
  // 是否正在运行中
  private static isAgentRunning: boolean = false

  // ----- 聊天数据核心 -----

  // 当前加载的消息
  private static messages: Ref<MessageInfo<any>[]>
  // 向Agent发送一条消息请求
  private static onRequest: (value: string) => void
  // 完整填充当前消息
  private static setMessages: (updater: MessageInfo<any>[]) => void

  /**
   * 服务初始化（全局仅执行一次）
   */
  private constructor() {
    // 模型调度
    const [agent] = useXAgent<string, { message: string }, string>({
      request: async ({}, { onSuccess, onUpdate }) => {
        if (!xChatAgent.chatSendingData) {
          xChatAgent.isAgentRunning = false
          onSuccess([`<error>Read conversation error, please retry</error>`])
          return
        }
        // 自定义错误处理
        const onError = async (convId: string, error: Error) => {
          xChatAgent.isAgentRunning = false
          // 记录错误消息
          await xChatService.getInstance().addConvChatInfo(convId, 'error', error.message)
          onSuccess([`<error>${error.message}</error>`])
        }
        // 读取当前聊天数据
        const { convId, model, messages, temperature, max_tokens } = xChatAgent.chatSendingData
        xChatAgent.isAgentRunning = true
        // 首次调用直接响应一个特殊的字符串用来判定loading状态
        // 该字段会在服务端成功响应后被覆盖，因此无需特殊处理
        onUpdate('[LOADING]')
        try {
          const provider = await xModelService.getInstance().getProviderInstance(model.supplier.id)

          // 调用聊天补全
          await provider.completions(
            {
              model: model.key,
              messages,
              temperature,
              max_tokens: max_tokens ? max_tokens : null,
            },
            {
              // 每次补全调用
              onUpdate,
              // 成功后调用
              onSuccess: async (message) => {
                xChatAgent.isAgentRunning = false
                // 添加本次响应的消息
                await xChatService.getInstance().addConvChatInfo(convId, 'assistant', message)
                onSuccess([message])
              },
              // 失败后调用
              onError: (error) => onError(convId, error),
            },
          )
        } catch (error) {
          await onError(convId, error)
          return
        }
      },
    })

    // 数据管理
    const xChat = useXChat({
      agent: agent.value,
      requestPlaceholder: '[LOADING]',
      requestFallback: t('account.requestError.timeout'),
    })

    xChatAgent.messages = xChat.messages
    xChatAgent.setMessages = xChat.setMessages
    xChatAgent.onRequest = xChat.onRequest
  }

  /**
   * 获取单例实例
   *
   * @returns
   */
  public static getInstance = () => {
    if (!xChatAgent.instance) {
      xChatAgent.instance = new xChatAgent()
    }
    return xChatAgent.instance
  }

  /**
   * 发送一条消息
   *
   * @param convId 会话ID（不存在表示新增）
   * @param modelId 模型ID
   * @param content 消息文本
   * @param attachs 附件内容
   * @param afterCreateChat 完成聊天创建之后会调用（注：调用时响应正在进行中，这里仅通知创建成功）
   * @returns 完整响应全部内容后才会返回，谨慎await
   */
  public send = async (
    convId: string,
    modelId: string,
    content: string,
    attachs: AttachFile[],
    afterCreateChat?: () => void,
  ) => {
    const model = await xModelService.getInstance().getModelById(modelId)
    if (!model?.inUse || !model?.supplier?.isActive) {
      throw new Error(t('model.modelSelectionError'))
    }
    // 发送新消息前，尝试打断前一次运行
    await this.abort()
    xChatAgent.isAgentRunning = true
    try {
      // 添加聊天数据
      await xChatService.getInstance().addConvChatInfo(convId, 'user', content, attachs)
      // 组装聊天参数
      const modelSetting = xModelService.getInstance().readModelSetting()
      xChatAgent.chatSendingData = {
        convId,
        model: await xModelService.getInstance().getModelById(modelId),
        messages: await xChatService.getInstance().getConvChatsHistory(convId),
        temperature: modelSetting.temperature,
        max_tokens: modelSetting.limitMaxTokens ? modelSetting.maxTokens : undefined,
      }
      await this.load(convId)
      // 发送agent，这里仅传递信号，内容不使用
      // 之前 load() 方法已经渲染了这段文本内容与附件，因此本段需要隐藏
      xChatAgent.onRequest('[HIDDEN]')
    } catch (error) {
      xChatAgent.isAgentRunning = false
      throw new Error(error)
    }

    if (typeof afterCreateChat === 'function') {
      afterCreateChat()
    }

    // 等待运行完成后，本方法才会返回
    return new Promise<void>((resolve) => {
      const checkStatus = async () => {
        if (!xChatAgent.isAgentRunning) {
          resolve()
        } else {
          setTimeout(checkStatus, 50)
        }
      }
      checkStatus()
    })
  }

  /**
   * 打断发送中的消息
   *
   * @returns
   */
  public abort = async () => {
    if (xChatAgent.isAgentRunning && xChatAgent.chatSendingData) {
      xChatAgent.isAgentRunning = false
      const { convId, model } = xChatAgent.chatSendingData
      try {
        // 执行provider打断逻辑
        const provider = await xModelService.getInstance().getProviderInstance(model.supplier.id)
        await provider.abort()
      } catch (error) {
        window.console.error(error)
      }
      // 添加中断说明
      await xChatService.getInstance().addConvChatInfo(convId, 'error', t('chat.abort.byUser'))
      await this.load(convId)
    } else {
      xChatAgent.isAgentRunning = false
    }
  }

  /**
   * 加载指定会话消息
   *
   * @param convId 会话ID
   * @returns
   */
  public load = async (convId: string) => {
    const messages: MessageInfo<any>[] = []
    if (convId) {
      const chats = await xChatService.getInstance().getConvChatsHistory(convId)
      for (const chat of chats) {
        if (chat.role === 'system') {
          // 系统消息
          messages.push({
            id: 'system-' + chat.id,
            status: 'success',
            message: t('chat.message.promptTip', { prompt: chat.content }),
          })
        } else {
          // 普通消息
          messages.push({
            id: chat.id,
            status: chat.role === 'user' ? 'local' : 'success',
            message: chat.content,
          })
          // 附件消息
          if (chat.attachs?.length) {
            messages.push({
              id: 'attach-' + chat.id,
              status: chat.role === 'user' ? 'local' : 'success',
              message: chat.attachs,
            })
          }
        }
      }
    }
    xChatAgent.setMessages(messages)
  }

  /**
   * 获取聊天泡泡属性
   *
   * @returns
   */
  public chatBubbleRoles = renderChatBubbles

  /**
   * 获取聊天泡泡数据（实时）
   *
   * @returns
   */
  public chatBubbleItems = computed(() => {
    const response: BubbleDataType[] = []
    for (const item of xChatAgent.messages.value) {
      if (item.message === '[HIDDEN]') continue
      // 渲染聊天数据
      if (item.id.toString().startsWith('system-')) {
        // 系统消息
        response.push({
          key: item.id,
          loading: false,
          role: 'system',
          content: item.message,
        })
      } else if (item.id.toString().startsWith('attach-')) {
        // 附件消息
        response.push({
          key: item.id,
          loading: false,
          role: 'file',
          content: item.message,
        })
      } else {
        // 普通消息
        let content = item.message.trim() //.replace(/<[^>]*>/g, '')
        if (!content) {
          content = '[LOADING]'
        }
        response.push({
          key: item.id,
          loading: content === '[LOADING]',
          role: item.status === 'local' ? 'me' : 'ai',
          content,
        })
      }
    }
    return response
  })
}

export default xChatAgent
